﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Sonce.Editor
{
	#region Matrix with row and column as int
	public class Matrix<T> where T : struct
	{
		#region Members
		private List<MatrixCell<T>> list;
		private int limit = 100; //Per dimension
		private bool isZeroBased = true;
		private bool ignoreErrors = true;
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new Matrix instance.
		/// </summary>
		public Matrix()
			: this(1000, true, true)
		{
		}

		/// <summary>
		/// Initializes a new Matrix instance.
		/// </summary>
		/// <param name="limit"></param>
		public Matrix(int limit)
			: this(limit, true, true)
		{
		}

		/// <summary>
		/// Initializes a new Matrix instance.
		/// </summary>
		public Matrix(int limit, bool isZeroBased, bool ignoreErrors)
		{
			this.list = new List<MatrixCell<T>>();
			this.ignoreErrors = ignoreErrors;
			this.isZeroBased = isZeroBased;
			this.limit = limit;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets a list of matrix fields.
		/// </summary>
		internal List<MatrixCell<T>> Cells
		{
			get
			{
				return this.list;
			}
		}

		/// <summary>
		/// Gets an array of columns.
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		public MatrixVirtualArray<T> this[int i]
		{
			get
			{
				if (Validate(i))
				{
					//Index inside range
					return new MatrixVirtualArray<T>(this, i);
				}
				else
				{
					//Index out of range, ignore error
					return MatrixVirtualArray<T>.Zero;
				}
			}
		}

		/// <summary>
		/// Gets a limit per dimension.
		/// </summary>
		public int Limit
		{
			get
			{
				return this.limit;
			}
		}

		/// <summary>
		/// Gets a value indincating whether the indices are zero-based.
		/// </summary>
		public bool IsZeroBased
		{
			get
			{
				return this.isZeroBased;
			}
		}

		/// <summary>
		/// Gets or sets a value indincating whether an exception is thrown on error.
		/// </summary>
		public bool IgnoreErrors
		{
			get
			{
				return this.ignoreErrors;
			}
			set
			{
				this.ignoreErrors = value;
			}
		}

		/// <summary>
		/// Gets the maximum row number.
		/// </summary>
		public int RowsCount
		{
			get
			{
				int count = 0;

				if (this.list != null && this.list.Count() > 0)
				{
					var rows = from x in this.list
										 orderby x.Row descending
										 select x.Row;

					if (rows != null && rows.Count() > 0)
					{
						count = rows.First() + 1;
					}
				}

				int shift = (this.isZeroBased ? 0 : 1);

				return count + shift;
			}
		}

		/// <summary>
		/// Gets the maximum column number.
		/// </summary>
		public int ColumnsCount
		{
			get
			{
				int count = 0;

				if (this.list != null && this.list.Count() > 0)
				{
					var cols = from x in this.list
										 orderby x.Column descending
										 select x.Column;

					if (cols != null && cols.Count() > 0)
					{
						count = cols.First() + 1;
					}
				}

				int shift = (this.isZeroBased ? 0 : 1);

				return count + shift;
			}
		}
		#endregion

		#region Methods
		/// <summary>
		/// Gets a human readable matrix string (plain text matrix).
		/// </summary>
		/// <returns></returns>
		public string Dump()
		{
			string result = string.Empty;
			int rowsCount = this.RowsCount;
			int colsCount = this.ColumnsCount;
			int shift = (this.isZeroBased ? 0 : 1);

			for (int i = shift; i < rowsCount && i < this.limit; i++)
			{
				for (int j = shift; j < colsCount && j < this.limit; j++)
				{
					if (j > shift) result += "\t"; //Insert tab
					result += this[i][j];
				}
				result += Environment.NewLine;
			}
			return result;
		}

		/// <summary>
		/// Clear all filled matrix fields.
		/// </summary>
		public void Clear()
		{
			this.list.Clear();
		}

		/// <summary>
		/// Check if index is inside bounds.
		/// </summary>
		/// <param name="index">Array index, either row or column.</param>
		/// <returns>Returns true if index is inside bouds.</returns>
		public bool Validate(int index)
		{
			if (this.isZeroBased && index >= 0 && index < this.limit)
			{
				return true;
			}
			else if (!this.isZeroBased && index > 0 && index <= this.limit)
			{
				return true;
			}
			else if (!this.ignoreErrors)
			{
				throw new ArgumentException("Index out of range!");
			}
			else
			{
				return false;
			}
		}
		#endregion
	}

	public class MatrixVirtualArray<T> where T : struct
	{
		#region Members
		private int i; //Primary array index
		private Matrix<T> matrix;
		#endregion

		#region Constructor
		/// <summary>
		/// Initializes a new MatrixVirtualArray instance.
		/// </summary>
		/// <param name="matrix">Parent Matrix instance.</param>
		/// <param name="i">Primary array index.</param>
		internal MatrixVirtualArray(Matrix<T> matrix, int i)
		{
			this.i = i;
			this.matrix = matrix;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets a matrix field value.
		/// </summary>
		/// <param name="j">Secondary array index.</param>
		/// <returns>Returns a matrix field value.</returns>
		public T this[int j]
		{
			get
			{
				if (matrix.Validate(j))
				{
					//Index inside range
					var cells = from x in this.matrix.Cells
											where x.Row == i && x.Column == j
											select x;

					if (cells != null && cells.Count() > 0)
					{
						var cell = cells.First();
						return cell.Value;
					}
				}

				//Returns empty value if it comes so far
				return default(T);
			}
			set
			{
				if (matrix.Validate(j))
				{
					var cells = from x in this.matrix.Cells
											where x.Row == i && x.Column == j
											select x;

					if (cells != null && cells.Count() > 0)
					{
						var cell = cells.First();
						cell.Value = value;
					}
					else
					{
						var cell = new MatrixCell<T>(i, j, value);
						this.matrix.Cells.Add(cell);
					}
				}
			}
		}

		/// <summary>
		/// Gets an empty, virtual matrix cell.
		/// </summary>
		public static MatrixVirtualArray<T> Zero
		{
			get
			{
				return new MatrixVirtualArray<T>(new Matrix<T>(), 0);
			}
		}
		#endregion
	}

	//TODO: Row or Column can have other any other type
	internal class MatrixCell<T> where T : struct
	{
		#region Properties
		/// <summary>
		/// Gets or sets the row number in matrix.
		/// </summary>
		public int Row
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the column number in matrix.
		/// </summary>
		public int Column
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a field value.
		/// </summary>
		public T Value
		{
			get;
			set;
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new MatrixCell instance.
		/// </summary>
		internal MatrixCell()
		{
		}

		/// <summary>
		/// Initializes a new MatrixCell instance
		/// </summary>
		internal MatrixCell(int row, int column, T value)
		{
			this.Row = row;
			this.Column = column;
			this.Value = value;
		}
		#endregion
	}
	#endregion

	#region Matrix with row and column as T
	public class Matrix<TIndex, TData>
	{
		#region Members
		private List<MatrixCell<TIndex, TData>> list;
		private int limit = 100; //Per dimension
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new Matrix instance.
		/// </summary>
		public Matrix()
			: this(1000)
		{
		}

		/// <summary>
		/// Initializes a new Matrix instance.
		/// </summary>
		public Matrix(int limit)
		{
			this.list = new List<MatrixCell<TIndex, TData>>();
			this.limit = limit;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets a list of matrix fields.
		/// </summary>
		internal List<MatrixCell<TIndex, TData>> Cells
		{
			get
			{
				return this.list;
			}
		}

		/// <summary>
		/// Gets an array of columns.
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		public MatrixVirtualArray<TIndex, TData> this[TIndex i]
		{
			get
			{
				return new MatrixVirtualArray<TIndex, TData>(this, i);
			}
		}

		/// <summary>
		/// Gets a limit per dimension.
		/// </summary>
		public int Limit
		{
			get
			{
				return this.limit;
			}
		}

		/// <summary>
		/// Gets the maximum row number.
		/// </summary>
		public int RowsCount
		{
			get
			{
				int count = 0;

				if (this.list != null && this.list.Count() > 0)
				{
					var rows = from x in this.list
										 orderby x.Row descending
										 select x.Row;

					if (rows != null && rows.Count() > 0)
					{
						count = rows.Count();
					}
				}

				return count;
			}
		}

		/// <summary>
		/// Gets the maximum column number.
		/// </summary>
		public int ColumnsCount
		{
			get
			{
				int count = 0;

				if (this.list != null && this.list.Count() > 0)
				{
					var cols = from x in this.list
										 orderby x.Column descending
										 select x.Column;

					if (cols != null && cols.Count() > 0)
					{
						count = cols.Count();
					}
				}

				return count;
			}
		}
		#endregion

		#region Methods
		/// <summary>
		/// Gets a human readable matrix string (plain text matrix).
		/// </summary>
		/// <returns></returns>
		public string Dump()
		{
			string result = string.Empty;
			int rowsCount = this.RowsCount;
			int colsCount = this.ColumnsCount;

			//for (int i = shift; i < rowsCount && i < this.limit; i++)
			//{
			//    for (int j = shift; j < colsCount && j < this.limit; j++)
			//    {
			//        if (j > shift) result += "\t"; //Insert tab
			//        result += this[i][j];
			//    }
			//    result += Environment.NewLine;
			//}

			return result;
		}

		/// <summary>
		/// Clear all filled matrix fields.
		/// </summary>
		public void Clear()
		{
			this.list.Clear();
		}
		#endregion
	}

	public class MatrixVirtualArray<TIndex, TData>
	{
		#region Members
		private TIndex i; //Primary array index
		private Matrix<TIndex, TData> matrix;
		#endregion

		#region Constructor
		/// <summary>
		/// Initializes a new MatrixVirtualArray instance.
		/// </summary>
		/// <param name="matrix">Parent Matrix instance.</param>
		/// <param name="i">Primary array index.</param>
		internal MatrixVirtualArray(Matrix<TIndex, TData> matrix, TIndex i)
		{
			this.i = i;
			this.matrix = matrix;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets a matrix field value.
		/// </summary>
		/// <param name="j">Secondary array index.</param>
		/// <returns>Returns a matrix field value.</returns>
		public TData this[TIndex j]
		{
			get
			{
				//Index inside range
				var cells = from x in this.matrix.Cells
										where x.Row.Equals(i) && x.Column.Equals(j)
										select x;

				//this.matrix.Cells.Select(x => x.Row == i && x.Column == j);

				if (cells != null && cells.Count() > 0)
				{
					var cell = cells.First();
					return cell.Value;
				}

				//Returns empty value if it comes so far
				return default(TData);
			}
			set
			{
				var cells = from x in this.matrix.Cells
										where x.Row.Equals(i) && x.Column.Equals(j)
										select x;

				if (cells != null && cells.Count() > 0)
				{
					var cell = cells.First();
					cell.Value = value;
				}
				else
				{
					var cell = new MatrixCell<TIndex, TData>(i, j, value);
					this.matrix.Cells.Add(cell);
				}
			}
		}

		/// <summary>
		/// Gets an empty, virtual matrix cell.
		/// </summary>
		public static MatrixVirtualArray<TIndex, TData> Zero
		{
			get
			{
				return new MatrixVirtualArray<TIndex, TData>(new Matrix<TIndex, TData>(), default(TIndex));
			}
		}
		#endregion
	}

	internal class MatrixCell<TIndex, TData>
	{
		#region Properties
		/// <summary>
		/// Gets or sets the row number in matrix.
		/// </summary>
		public TIndex Row
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets the column number in matrix.
		/// </summary>
		public TIndex Column
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a field value.
		/// </summary>
		public TData Value
		{
			get;
			set;
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new MatrixCell instance.
		/// </summary>
		internal MatrixCell()
		{
		}

		/// <summary>
		/// Initializes a new MatrixCell instance
		/// </summary>
		internal MatrixCell(TIndex row, TIndex column, TData value)
		{
			this.Row = row;
			this.Column = column;
			this.Value = value;
		}
		#endregion
	}
	#endregion
}
